package com.carrot.gateway.filter;

import com.carrot.gateway.entity.AccessLog;
import com.google.common.base.Joiner;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR;

/**
 * 访问日志收集
 * @author liuhongzhen
 */
@Component
@Slf4j
@AllArgsConstructor
public class LoggingGlobalFilter implements GlobalFilter, Ordered {

    private static Joiner joiner = Joiner.on("");

    private final RedisTemplate<String, Object> redisTemplate;
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        AccessLog accessLog=new AccessLog(redisTemplate);
        accessLog.buildRequestLog(request);
        //下游服务sleuth需要的请求头
        ServerHttpRequest newRequest = request.mutate()
                .header("X-B3-TraceId", accessLog.getTraceId())
                .header("X-B3-Sampled","1")
                .header("X-B3-SpanId",accessLog.getTraceId().substring(0,16)).build();
        // 执行过滤器链，捕获响应信息
        //ServerHttpResponse newResponse = newServerHttpResponse(response, exchange,accessLog);
        return chain.filter(exchange.mutate().request(newRequest).build())
                .doOnSuccess(r->{
                    //下游响应状态码
                    accessLog.setResponseHttpCode(response.getStatusCode().value());
                    ConsoleLogAndBuildResponseLog(accessLog);
                 }).doOnError(r->{
                     //网关服务错误
                    accessLog.setResponseParam(r.getMessage());
                    accessLog.setResponseHttpCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
                    ConsoleLogAndBuildResponseLog(accessLog);
                });
    }


    private void ConsoleLogAndBuildResponseLog(AccessLog accessLog){
        try {
            accessLog.buildResponseTime();
            log.info(accessLog.toString());
        }catch (Exception e){
            log.error("访问日志记录失败:{}",e.getMessage());
        }
    }

    /**
     * 通过重写writeWith 获取响应信息
     */
    private ServerHttpResponse newServerHttpResponse(ServerHttpResponse sourceServerHttpResponse,ServerWebExchange exchange,AccessLog accessLog){

        DataBufferFactory dataBufferFactory = sourceServerHttpResponse.bufferFactory();
        ServerHttpResponseDecorator responseDecorator=new ServerHttpResponseDecorator(sourceServerHttpResponse){
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    String originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
                    // 获取ContentType，判断是否返回JSON格式数据
                    if (StringUtils.isNotBlank(originalResponseContentType) && originalResponseContentType.contains("application/json")) {
                        Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                        //（返回数据内如果字符串过大，默认会切割）解决返回体分段传输
                        return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                            List<String> list = Lists.newArrayList();
                            dataBuffers.forEach(dataBuffer -> {
                                try {
                                    byte[] content = new byte[dataBuffer.readableByteCount()];
                                    dataBuffer.read(content);
                                    DataBufferUtils.release(dataBuffer);
                                    list.add(new String(content, "utf-8"));
                                } catch (Exception e) {
                                    log.info("加载Response字节流异常，失败原因：{}", Throwables.getStackTraceAsString(e));
                                }
                            });
                            String responseData = joiner.join(list);
                            accessLog.buildResponseBody(HttpStatus.OK,responseData);
                            byte[] uppedContent = new String(responseData.getBytes(), Charset.forName("UTF-8")).getBytes();
                            sourceServerHttpResponse.getHeaders().setContentLength(uppedContent.length);
                            return dataBufferFactory.wrap(uppedContent);
                        }));
                    }
                }else if(body instanceof Mono){
                        Mono<? extends DataBuffer> monoBody = Mono.from(body);
                        Mono<DataBuffer> map = monoBody.map(dataBuffer -> {
                            String responseData = "";
                            try {
                                byte[] content = new byte[dataBuffer.readableByteCount()];
                                dataBuffer.read(content);
                                DataBufferUtils.release(dataBuffer);
                                responseData = new String(content, StandardCharsets.UTF_8);
                                accessLog.buildResponseBody(HttpStatus.OK,responseData);
                            } catch (Exception e) {
                                log.info("加载Response字节流异常，失败原因：{}", Throwables.getStackTraceAsString(e));
                            }
                            byte[] uppedContent = new String(responseData.getBytes(), Charset.forName("UTF-8")).getBytes();
                            sourceServerHttpResponse.getHeaders().setContentLength(uppedContent.length);
                            return dataBufferFactory.wrap(uppedContent);
                        });
                        return super.writeWith(map);
                }
                return super.writeWith(body);
            }

            @Override
            public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
                return writeWith(Flux.from(body).flatMapSequential(p->p));
            }
        };

        return responseDecorator;
    }


    @Override
    public int getOrder() {
        return -1;
    }
}
